package siedler.GUI;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.rmi.RemoteException;
import java.util.ArrayList;
import javax.swing.JPanel;

import siedler.GUI.gameElements.*;
import siedler.GUI.interfaces.*;
import siedler.GUI.other.*;
import siedler.communication.Move;
import siedler.communication.PeerI;
import siedler.communication.Peer;
import util.simpleIO.Out;

@SuppressWarnings("serial")
public class SiedlerGamePanel extends JPanel implements ActionListener, MouseListener, KeyListener, ComponentListener {

	
	//-----------------------------------------------------------------
	// --- Game specific methods ------------------------------------
	//
	
	//-----------------------------------------------------------------
	// --- Graphic variables ------------------------------------------
	//
	
	public final static int    x6E  = 6;						// number of edges/connection
	public final static int    xAdd = x6E*10;					// value to add before modulo for index <= 0
	final static double SIN30 = 0.5, /*Math.sin(2.0*Math.PI/360.0*30.0);*/ 
						COS30 = Math.cos(2.0*Math.PI/360.0*30.0);
	final static int[][] FIELD_CONNECTIONS = { 	{1,   0, 2, 5, 4, 0, 0},		// predefined connections for each field
												{2,   0, 3, 6, 5, 1, 0},
												{3,   0, 0, 7, 6, 2, 0},
												{4,   1, 5, 9, 8, 0, 0},
												{5,   2, 6,10, 9, 4, 1},
												{6,   3, 7,11,10, 5, 2},
												{7,   0, 0,12,11, 6, 3},
												{8,   4, 9,13, 0, 0, 0},
												{9,   5,10,14,13, 8, 4},
												{10,  6,11,15,14, 9, 5},
												{11,  7,12,16,15,10, 6},
												{12,  0, 0, 0,16,11, 7},
												{13,  9,14,17, 0, 0, 8},
												{14, 10,15,18,17,13, 9},
												{15, 11,16,19,18,14,10},
												{16, 12, 0, 0,19,15,11},
												{17, 14,18, 0, 0, 0,13},
												{18, 15,19, 0, 0,17,14},
												{19, 16, 0, 0, 0,18,15},
											  };
	
	protected int fieldSize, fx, fy;			// size of one field and distances between fields
	private boolean firstSizeInitDone=false;	// true after first initialization
	
	//public final static Pictures pictures = new Pictures();
	//protected Coordinate base;  // = new Coordinate(size,size,size,size);
	Field[] fields;
	ArrayList<Paintable> paintableList = new ArrayList<Paintable>();
	ArrayList<Klickable> klickableList = new ArrayList<Klickable>();
	ConnectionList connectionList;
	CornerList cornerList;
//	boolean placeTown, placeCity, placeStreet;
	
	public Move myMove = null;
	public boolean receivedMove = false;
	
	
	//-----------------------------------------------------------------
	// --- Initialization ---------------------------------------------
	//
	
	/**
	 * Constructor
	 * @param name	- a name for this siedler panel
	 */
	public SiedlerGamePanel(String name, PeerI peer){
		setName(name);				// set panel name in superclass
		
		//Pictures.initPictures();
		//Field.gamePanel = this;	// set this game panel for the fields
		makeFields();				// make fields (19)
		makeConnections();			// make field connections
		makeCorners();				// make field corners
		
		((Peer) peer).initGamePlay(fields, this);	// TODO tmp
		
		//Field.gamePanel = this;
		//Connection.gamePanel = this;
	}
	
	/**
	 * initiate and resize graphics 
	 */
	protected void initGraphics(){
		
		// --- SIZEs --- -----------------------------------------
		//
		
		// --- s is minumum of width/height ---
		int s; 
		if(this.getWidth()<this.getHeight()) 
			s=getWidth(); 
		else 
			s=getHeight(); 
		
		// --- calculate the size of an field ---
		fieldSize=(int)(s/5.0);
		
		if(fieldSize%2==1) 						// field size can be divided by 2
			fieldSize++;
		
		// --- calculate field distances ---
		double radius = fieldSize/2;			// field radius
		fx = (int)(radius*COS30*2);				// x distance of fields
		if(fx%2==1) 							// fx can be divided by 2
			fieldSize++;
		fy = (int)(radius*SIN30+radius);		// y distance of fields
		
		// --- FIELDS --- ----------------------------------------
		//
		
		// --- get the coordinates for the fields ---
		Coordinate[] coords = calculateFieldCoordinates(fieldSize, fx,fy);	// calculate field coordinates
		if(coords.length != fields.length)
			throw new RuntimeException("As much fields as coordinates needed.");
		
		// --- initiate field positions ---
		for(int i=0; i<fields.length; i++){	
			fields[i].initGraphics(coords[i], fieldSize);	// set field coordinates
		}
		
		// --- CONNECTIONS --- ------------------------------------
		//
		
		// --- initiate connection positions ---
		connectionList.initAllConnections();
		
		// --- CORNERS --- ----------------------------------------
		//
		
		// --- initiate corner positions ---
		cornerList.initAllCorners();
		
		// --- FIRST INIT --- -------------------------------------
		//
		if(!firstSizeInitDone){
			
			addPaintables(fields);
			addPaintables(connectionList.getConnections());
			addPaintables(cornerList.getCorners());
			
			addKlickable(fields);
			addKlickable(connectionList.getConnections());
			addKlickable(cornerList.getCorners());
			
			firstSizeInitDone=true;
		}
	}
	
	/**
	 * makeFields
	 * @return ArrayList<Field> - size=19
	 */
	public void makeFields(){
		
		fields = new Field[19];
		
		for(int i=0; i<fields.length; i++){
			fields[i] = new Field(i+1);
		}
		//return fields;	//fields.toArray(new Field[0])
	}
	
	/**
	 * getFieldCoordinates
	 * @param fieldSize 	- size of one field element
	 * @return Coordinate[] - size=19
	 */
	public static Coordinate[] calculateFieldCoordinates(int fieldSize, int fx, int fy) {
		
		//double[][] list = new double[19][2];
		ArrayList<Coordinate> list = new ArrayList<Coordinate>(19);
		//double[]   run  = new double[2];
		Coordinate base = new Coordinate(fieldSize,fieldSize);
		Coordinate run = null;
		int i;
		
		for(i=1; i<=19; i++){
			
			if(i==1)  									// 1st line
				run = base.copy().add(2*fx/2, 0*fy);
			else if(i==4)   							// 2nd line
				run = base.copy().add(1*fx/2, 1*fy);
			else if(i==8)   							// 3rd line
				run = base.copy().add(0*fx/2, 2*fy);
			else if(i==13)   							// 4th line
				run = base.copy().add(1*fx/2, 3*fy);
			else if(i==17)   							// 5th line
				run = base.copy().add(2*fx/2, 4*fy);
			
			list.add(run.copy());		// add coordinate 'run'
			run.add(fx, 0);				// add x-distance 'fx'
		}
		
		return list.toArray(new Coordinate[0]);
	}
	
	/**
	 * connectionList: Set all connections between fields and connections between connections.
	 */
	private void makeConnections(){
		
		connectionList = new ConnectionList();
		
		// --- error management: check number of defined connections---
		if(fields.length != FIELD_CONNECTIONS.length)
			throw new RuntimeException("Lengths should both be 19...");
		
		// --- run through all fields and add connections --- -----------------------------
		//
		for(int i=0; i<fields.length; i++){
			
			// --- init ---
			Field f1 = fields[i];									// the current field
			int[] connections = FIELD_CONNECTIONS[i];				// the connections to make for the current field
			Connection[] fieldConnections = new Connection[x6E];		// the new connections to be added to the field
			
			// --- error management: check defined connections ---
			if(connections.length != x6E+1)
				throw new RuntimeException("Length should be 7...");
			if(getFieldNumber(f1)!=connections[0])
				throw new RuntimeException("Field number does't fit at number "+(i+1));
			
			// --- add field connections ---
			for(int j=1; j<connections.length; j++){
				
				int index = connections[j]-1;			// target field index
				Field f2;
				if(index<0)								// Field is null with negative index
					f2 = null;							// -
				else									// Field is specified field with index>0
					f2 = fields[index];					// -
				
				Connection c2;
				c2 = new Connection(f1, f2, j);					// make new connection between f1 and f2
				c2 = connectionList.addConnection(c2);			// try to add new connection. May return existing connection.
				fieldConnections[j-1] = c2;						// store final connection in array.
			}
			f1.setConnections(fieldConnections);				// set connection array (for identification of the connections)
		}
		
		// --- connect all field-connections --- ----------------------------------------
		//
		for(int i=0; i<fields.length; i++){
			
			// --- init ---
			Field f = fields[i];	// the current field
			Connection[] fConnections = f.getConnections();
			
			// --- error management: not more than 6 connections per field ---
			{
				ArrayList<Connection> tmp = connectionList.getConnections(f);	
				if(tmp.size()!=x6E)
					throw new RuntimeException("Length mus be 6. ("+f+", "+tmp.size()+" connections)");
			}
			
			//Out.pl("\nStart connecting: "+f );
			for(int j=0; j<fConnections.length; j++){
				
				//Out.pl("Connection " + (j+1));
				
				// --- direct connections ---
				Field f2;
				Connection c1 = fConnections[j];				// current connection
				Connection cNext = f.nextConnection(c1);		// next connection
				Connection cPrev = f.previousConnection(c1);	// previous connection
				
				// --- add previous and next connection ---
				c1.connectedTo(cPrev);							// connect to ancestor
				c1.connectedTo(cNext);							// connect to successor
				
				// --- add connections to bordering fields (previous, next, direct) ---
				f2 = cPrev.getTarget(f);						// connect to other connection: previous Field
				if(f2 != null)									// if existent:
					c1.connectedTo(f2.previousConnection(cPrev)); // (previous field, previous of previous connection)
				
				f2 = cNext.getTarget(f);						// connect to other connection: next Field
				if(f2 != null)									// if existent:
					c1.connectedTo(f2.nextConnection(cNext));	//  (next field, next of next connection)
				
				f2 = c1.getTarget(f);							// connect to other connection: direct connected Field
				if(f2 != null){									// if existent:
					c1.connectedTo(f2.nextConnection(c1));		//  (direct field, next of this connection)
					c1.connectedTo(f2.previousConnection(c1));	//  (direct field, previous of this connection)
				}
				
				c1.finalTest();		// internal test for correctness.
			}//_for j_
		}//_for i_
		
	}
	
	/**
	 * cornerList: Make and connect the corners of every field
	 */
	public void makeCorners(){
		cornerList = new CornerList();
		
		// --- make corners for all fields --- ----------------------------------------
		//
		for(int i=0; i<fields.length; i++){
			
			Field f = fields[i];
			Corner[] fieldCorners = new Corner[x6E];
			
			for(int j=1; j<=x6E; j++){
				int cornerNumber = j;
				
				ArrayList<Field> fields 		  = f.getCornerFields  (cornerNumber);
				ArrayList<Connection> connections = f.getCornerConnections(cornerNumber);
				
				Corner c = new Corner(fields, connections, j);	// make corner
				c = cornerList.addCorner(c);					// try to add new Corner. May return existing Corner.
				
				fieldCorners[j-1] = c;// store final Corner in array.
			}
			f.setCorners(fieldCorners);
		}
	}
	
	//-----------------------------------------------------------------
	// --- object specific methods ------------------------------------
	//
	
	/**
	 * Get field number of specified field.
	 * Field number start at 1 and and at 19.
	 * @param fs
	 * @return int 1...19
	 */
	public int getFieldNumber(Field fs){
		
		for(int i=0; i<fields.length; i++){
			if(fs == fields[i])
				return i+1;
		}
		throw new RuntimeException("Field does not exist!");
	}
	
	
//	public Field getFieldByNumber(int number){
//		return fields[ (number-1+x6E)%x6E ];
//	}
	


	
	//-----------------------------------------------------------------
	// --- Painting ---------------------------------------------------
	//
	
	public void setMyMove(Move move) {
		this.myMove = move;
	}
	
	public void setReceivedMove(boolean received) {
		this.receivedMove = received;
	}
	
	private static int paintCall=0;
	@Override
	protected void paintComponent(Graphics g) {		// overwritten component method: does the painting
		super.paintComponent(g);
		
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		
		Font f = new Font(Font.SERIF, Font.PLAIN, 16);
		g.setFont(f);
		
		g.setColor(Color.white);				// color for lines
		
		paintCall++;
		Out.pl(""+ paintCall+") Paint "+paintableList.size()+" elements...");
		for(Paintable p : paintableList){		// -
			//Out.pl("   paint " + p);
			p.paint(g);							// -
		}
	}
	
	/**
	 * addPaintables
	 * @param paintables
	 */
	private void addPaintables(Paintable[] paintables){
		for(Paintable p : paintables)
			paintableList.add(p);
	}
	
	//-----------------------------------------------------------------
	// --- Klickable methods ------------------------------------------
	//
	
	/**
	 * Klicks the first Klickable which is found at this position.
	 * @param x
	 * @param y
	 */
public void klickKlickable(int x, int y){
		
		int[] moveCoord = new int[2];
		moveCoord[0] = x;
		moveCoord[1] = y;
		
		if(myMove != null) {
			System.out.println("klickKlickable Method");
			
			for(Klickable k : klickableList){
				if(k.isKlickedBy(x, y)) {
					
					if(k instanceof Field){
						for(Field f : fields)
							f.reset();}
					else {
						try {
							System.out.println("Player " + myMove.getOwner().getId() + "has klicked something");
						} catch (RemoteException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						myMove.addBuilding(moveCoord);
						boolean doRepaint = k.klickIt();
						if(doRepaint)
							this.repaint();
						return;
					}
				}		
			} //for
		}//if
		else if(receivedMove) {
			System.out.println("Trying to draw received move");
			
			for(Klickable k : klickableList){
				if(k.isKlickedBy(x, y)) {
					
					if(k instanceof Field){
						for(Field f : fields)
							f.reset();}
					else {
						System.out.println("FINALLY drawing the move");
						boolean doRepaint = k.klickIt();
						if(doRepaint)
							this.repaint();
						return;
					}
				}		
			} //for
		}
		else {
			System.out.println("You are not allowed to klick!");
		}
	}
	
	public Klickable getFirstKlickable(int x, int y){
		
		for(Klickable k : klickableList){
			if(k.isKlickedBy(x, y))
				return k;
		}
		return null;
	}
	
	/**
	 * addKlickable
	 * @param klickables
	 */
	private void addKlickable(Klickable[] klickables) {
		for(Klickable k : klickables)
			klickableList.add(k);
	}

	
	//-----------------------------------------------------------------
	// --- Mouse Events (MouseListener) -------------------------------
	//
	
	@Override
	public void mousePressed(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		Out.pl("mousePressed " + x +"/"+ y);
	}
	@Override
	public void mouseReleased(MouseEvent e) {
		//int x = e.getX();
		//int y = e.getY();
		//Out.pl("mouseReleased " + x+"/"+y);
	}
	@Override
	public void mouseClicked(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		klickKlickable(x,y);
		//Out.pl("mouseClicked " + x+"/"+y + "    found "+this.findComponentAt(x, y));
		//this.findComponentAt(x, y);
	}
	@Override
	public void mouseEntered(MouseEvent e) {
	}
	@Override
	public void mouseExited(MouseEvent e) {
	}

	//-----------------------------------------------------------------
	// --- Action Events (ActionListener) -----------------------------
	//
	
	@Override
	public void actionPerformed(ActionEvent e) {
		if(myMove != null){
			if(e.getActionCommand() == "End Move") {
				try {
					myMove.getOwner().endMove();
				} catch (RemoteException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		}
		else {System.out.println("Not your turn!");}
		
	}

	//-----------------------------------------------------------------
	// --- Key Events (KeyListener) -----------------------------------
	//
	
	@Override
	public void keyPressed(KeyEvent e) {
		Out.pl("keyPressed " + e.getKeyChar());
		//char key = Character.toUpperCase(e.getKeyChar());	// always use the upper case
	}
	@Override
	public void keyReleased(KeyEvent e) {
		// do nothing
	}
	@Override
	public void keyTyped(KeyEvent e) {
		// do nothing
	}
	
	//-----------------------------------------------------------------
	// --- Component Events  (ComponentListener) ----------------------
	//
	
	@Override
	public void componentHidden(ComponentEvent e) {
		// ignore
	}
	@Override
	public void componentMoved(ComponentEvent e) {
		// ignore
	}
	@Override
	public void componentResized(ComponentEvent e) {
		//Out.pl("> Component resize");
		initGraphics();
		//repaint();
	}
	@Override
	public void componentShown(ComponentEvent e) {
		// ignore
	}
	
}
